/**
* 
* Copyright [2007] [Sunny Liu]
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
*   Unless required by applicable law or agreed to in writing, software
*   distributed under the License is distributed on an "AS IS" BASIS,
*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*   See the License for the specific language governing permissions and
*   limitations under the License.
*/
package ${appConfig.outputPackageName}.xml;


import java.beans.PropertyDescriptor;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;


public class SimpleBeanXMLSerializer 
{	
	public static final int SIMPLE_PROPERTY = 0;
	public static final int COLLECTION_PROPERTY = 1;	
	public static final int MAP_PROPERTY = 2;
	public static final int CLASS_PROPERTY = 3;
	public static final String MAP_ENRTY_ELEMENT_NAME = "entry";
	public static final String MAP_ENRTY_ATT_KEY = "key";
	public static final String MAP_ENRTY_ATT_VALUE = "value";
	
	
	
	public static void serializeToXML(Object bean,  OutputStream out)
	{
		serializeToXML(bean, null, null, new StreamResult(out));
	}
	
	public static void serializeToXML(Object bean,  Writer out)
	{
		serializeToXML(bean, null, null, new StreamResult(out));
	}
	
	public static void serializeToXML(Object bean, Document doc, OutputStream out)
	{
		serializeToXML(bean, doc, null, new StreamResult(out));
	}
	
	public static void serializeToXML(Object bean, Document doc,  Writer out)
	{
		serializeToXML(bean, doc, null, new StreamResult(out));
	}
	
	public static void serializeToXML(Object bean, Document doc, Element parent, OutputStream out)
	{
		serializeToXML(bean, doc, parent, new StreamResult(out));
	}
	
	public static void serializeToXML(Object bean, Document doc, Element parent, Writer out)
	{
		serializeToXML(bean, doc, parent, new StreamResult(out));
	}
	
	protected static void serializeToXML(Object bean, Document doc, Element parent, Result result)
	{
		Document doc1 = serializeToXML(bean, doc, parent);
		try {            
            Source source = new DOMSource(doc1);
            //Result result = new StreamResult(out);
            TransformerFactory factory = TransformerFactory.newInstance();            
            Transformer xformer = factory.newTransformer();
            xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            xformer.setOutputProperty(OutputKeys.INDENT, "yes");    
            xformer.setOutputProperty(OutputKeys.STANDALONE, "yes");  
            xformer.transform(source, result);
        } catch (TransformerConfigurationException e) {
        } catch (TransformerException e) {
        }
	}
	
	
	public static Document serializeToXML(Object bean, Document doc, Element parent)
	{
		if (bean==null) return null;
		if(doc==null){
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	        try {
	          DocumentBuilder builder = factory.newDocumentBuilder();	          
	          doc = builder.newDocument();	          	          
	        } catch (Exception sxe) {
	          throw new RuntimeException("Failed to build DOM document.", sxe);
	        }
		}		
		int type = objectType(bean);
		switch(type)
		{
			case COLLECTION_PROPERTY:
				if(parent==null){
					parent = doc.createElement("Collection");
					doc.appendChild(parent);
				}
				toXMLElement((Collection)bean, doc, parent);				
				break;
			case MAP_PROPERTY:
				if(parent==null){
					parent = doc.createElement("Map");
					doc.appendChild(parent);
				}
				toXMLElement((Map)bean, doc, parent);
				break;		
			  default:				
				Element el = toXMLElement(bean, PropertyUtils.getPropertyDescriptors(bean), doc);
	        	if(parent==null){
					doc.appendChild(el);
					return doc;			
				}else{
					parent.appendChild(el);
				}
	          break;
		}
		return doc;
	}
	
	
	
	protected static void toXMLElement(Collection beans, Document doc, Element parent)
	{
		if(beans==null || beans.size()==0) return;
		Object colMember = beans.iterator().next();
		if(isSimpleType(colMember)){
			for(Iterator iterator=beans.iterator(); iterator.hasNext();)
			{
				Object member = iterator.next().toString();	
				if(member!=null){
					Element child = doc.createElement(MAP_ENRTY_ELEMENT_NAME);
					if(child!=null){
							parent.appendChild(child);
						Text txtNode = doc.createTextNode(member.toString());
						child.appendChild(txtNode);
					}
				}
			}	
		}else{		
			PropertyDescriptor[] propDescriptors = 
					PropertyUtils.getPropertyDescriptors(colMember);
			for(Iterator iterator=beans.iterator(); iterator.hasNext();)
			{
				Element child = toXMLElement(iterator.next(), propDescriptors, doc);
				if(child!=null){
					parent.appendChild(child);
				}
			}	
		}
	}
	
	protected static void toXMLElement(Map beans, Document doc, Element parent)
	{
		if(beans==null || beans.size()==0) return;		
		for(Iterator iterator=beans.keySet().iterator(); iterator.hasNext();)
		{			
			Object key = (Object)iterator.next();
			Object value = beans.get(key);
			if(key!=null && value!=null)
			{
				Element child = doc.createElement(MAP_ENRTY_ELEMENT_NAME);
				parent.appendChild(child);
				child.setAttribute(MAP_ENRTY_ATT_KEY, key.toString());
				child.setAttribute(MAP_ENRTY_ATT_VALUE, value.toString());
			}
			
		}				
	}
	
	protected static String getClassSimpleName(Class clazz)
	{
		String name = clazz.getName();
		int idx = name.lastIndexOf(".");
		if(idx!=-1){
			name = name.substring(idx+1);
		}
		return name;
	}
	
	protected static Element toXMLElement(Object bean, PropertyDescriptor[] propDescriptors, Document doc)
	{
		if(bean==null) return null;
		//String name = bean.getClass().getSimpleName();
		String name =getClassSimpleName(bean.getClass());
		Element el = doc.createElement(name);
		for (int i=0; i<propDescriptors.length; i++)
		{
			try {
				PropertyDescriptor descriptor = propDescriptors[i];
				switch(propertyType(descriptor))
				{
					case COLLECTION_PROPERTY:
						Collection collection = (Collection)
								descriptor.getReadMethod().invoke(bean, null);
						if(collection!=null && collection.size()==0) {
							Element child = doc.createElement(descriptor.getName());
							el.appendChild(child);
						}else if(collection!=null && collection.size()>0){
							Element child = doc.createElement(descriptor.getName());
							el.appendChild(child);
							toXMLElement(collection, doc, child);
						}						
						break;	
					case MAP_PROPERTY:
						Map map = (Map)
								descriptor.getReadMethod().invoke(bean, null);
						if(map!=null && map.size()==0) {
							Element child = doc.createElement(descriptor.getName());
							el.appendChild(child);
						}else if(map!=null && map.size()>0){
							Element child = doc.createElement(descriptor.getName());
							el.appendChild(child);
							toXMLElement(map, doc, child);
						}			
						break;
					case CLASS_PROPERTY:
						//This is internal used properties, we should ignore it.
						break;
					case SIMPLE_PROPERTY:
					default:
						String attName = descriptor.getName();
						String value = BeanUtils.getSimpleProperty(bean, attName);
						if(value!=null){;
							el.setAttribute(attName, value);
						}	
						break;				
				}
			}  catch (Exception e) {				
				e.printStackTrace();
			}			
		}
		return el;
	}
	
	
	
	protected static boolean isCollectionProperty(PropertyDescriptor descriptor)
	{
		return isPropertyOfTargetType(descriptor, Collection.class);
	}
	
	protected static boolean isMapProperty(PropertyDescriptor descriptor)
	{
		return isPropertyOfTargetType(descriptor, Map.class);
	}
	
	protected static boolean isSetProperty(PropertyDescriptor descriptor)
	{
		return isPropertyOfTargetType(descriptor, Set.class);
	}
	
	protected static boolean isPropertyOfTargetType(PropertyDescriptor descriptor, Class clazz)
	{
		if(descriptor.getPropertyType()==clazz){
			return true;
		}
		Class[] clazzes = descriptor.getPropertyType().getInterfaces();
		for (int i=0; i<clazzes.length; i++){
			System.out.println(clazzes[i].getName());
			if(clazzes[i]==clazz){
				return true;
			}
		}
		return false;
	}
	
	protected static int propertyType(PropertyDescriptor descriptor)
	{	
		Class[] clazzes1 = descriptor.getPropertyType().getInterfaces();
		Class[] clazzes2 = descriptor.getPropertyType().getClasses();
		Class[] clazzes = new Class[1+clazzes1.length+clazzes2.length];
		clazzes[0] = descriptor.getPropertyType();
		System.arraycopy(clazzes1, 0, clazzes, 1, clazzes1.length);
		System.arraycopy(clazzes1, 0, clazzes, 1+clazzes1.length, clazzes2.length);
				
		for (int i=0; i<clazzes.length; i++){
			//System.out.println(clazzes[i].getName());
			if(clazzes[i]==List.class || clazzes[i]==Set.class || clazzes[i]==Collection.class){
				return COLLECTION_PROPERTY;
			}else if (clazzes[i]==Map.class){
				return MAP_PROPERTY;
			}else if (clazzes[i]==Class.class){
				return CLASS_PROPERTY;
			}			
		}
		return SIMPLE_PROPERTY;		
	}
	
	
	protected static int objectType(Object bean)
	{				
		Class[] clazzes1 = bean.getClass().getInterfaces();
		Class[] clazzes2 = bean.getClass().getClasses();
		Class[] clazzes = new Class[1+clazzes1.length+clazzes2.length];
		clazzes[0] = bean.getClass();
		System.arraycopy(clazzes1, 0, clazzes, 1, clazzes1.length);
		System.arraycopy(clazzes1, 0, clazzes, 1+clazzes1.length, clazzes2.length);
				
		for (int i=0; i<clazzes.length; i++){
			//System.out.println(clazzes[i].getName());
			if(clazzes[i]==List.class || clazzes[i]==Set.class || clazzes[i]==Collection.class){
				return COLLECTION_PROPERTY;
			}else if (clazzes[i]==Map.class){
				return MAP_PROPERTY;
			}else if (clazzes[i]==Class.class){
				return CLASS_PROPERTY;
			}			
		}
		return SIMPLE_PROPERTY;
	}
	
	protected static boolean isSimpleType(Object bean)
	{				
		Class clazz = bean.getClass();
		if(clazz.getPackage() == Integer.class.getPackage()) return true;
		if(clazz.getClass().isPrimitive()) return true;			
		return false;		
	}
	
}
